﻿using System;
using System.Configuration;
using System.Reflection;
using FluentNHibernate.Automapping;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;

namespace Machinery.Cogs
{
    public interface INHibernateConfigurationCog
    {
        NHibernate.Cfg.Configuration CreateConfiguration();
    }

    public class NHibernateConfigurationCog : Cog, INHibernateConfigurationCog
    {
        // ReSharper disable InconsistentNaming
        private const string MSSQL = "mssql";
        private const string mySQL = "mysql";
        // ReSharper restore InconsistentNaming


        protected virtual void OverrideMappings(AutoPersistenceModel configuration)
        {
            // Customize automappings here, if needed.
            // http://wiki.fluentnhibernate.org/Auto_mapping


        }

        protected virtual void OverrideConfiguration(FluentConfiguration configuration)
        {
            // Customize configuration here, if needed.
            // http://wiki.fluentnhibernate.org/Auto_mapping
        }


        public NHibernate.Cfg.Configuration CreateConfiguration()
        {
            Assembly persistentAssembly = null;
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
                if (assembly.FullName != null && assembly.FullName.Contains("Persistent"))
                    persistentAssembly = assembly;

            if (persistentAssembly == null)
                throw new ConfigurationErrorsException("No assembly with \"Persistent\" in the name was found. Machinery+Cogs projects expects this to be present, containing only data objects. Make sure you have referenced the Persistent assebly from your main assembly? ");
            
            var configuration = Fluently.Configure().Database(CreateDatabaseConfiguration());
            // Workaround for “Could not find any resources appropriate for the specified culture or the neutral culture” http://bit.ly/hRorr
            configuration.ExposeConfiguration(c => c.Properties.Add(global::NHibernate.Cfg.Environment.Hbm2ddlKeyWords, "none"));
            OverrideConfiguration(configuration);

            configuration.Mappings(m => m.FluentMappings.AddFromAssembly(persistentAssembly));
            configuration.Mappings(m => m.AutoMappings.Add(AutoMap.Assembly(persistentAssembly).Where(type => typeof(IPersistentEntity) == type.GetInterface(typeof(IPersistentEntity).FullName))));
            var model = new AutoPersistenceModel();
            model.AddEntityAssembly(persistentAssembly);
            model.Where(type => typeof(IPersistentEntity) == type.GetInterface(typeof(IPersistentEntity).FullName));
            OverrideMappings(model);



            return configuration.BuildConfiguration();
        }



        /// <summary>
        /// Creates an initial Fluent IPersistenceConfigurer from the database specified in app/web.config. 
        /// Looks for a connectionstring with key "mysql" or "mssql". If not found, it returns a configuration forn an in-memory sqlite db.
        /// </summary>
        /// <returns></returns>
        private static IPersistenceConfigurer CreateDatabaseConfiguration()
        {
            if (ConnectionString(mySQL) != null)
                return MySQLConfiguration.Standard.ConnectionString(ConnectionString(mySQL));
            if (ConnectionString(MSSQL) != null)
                return MsSqlConfiguration.MsSql2005.ConnectionString(ConnectionString(MSSQL));
            
            return SQLiteConfiguration.Standard.InMemory().Raw(global::NHibernate.Cfg.Environment.ReleaseConnections, "on_close");
            
        }

        /// <summary>
        /// Shorthand for connection string from configuration file (app.config/web.config).
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        private static string ConnectionString(string key)
        {
            var connectionStringFromConfig = ConfigurationManager.ConnectionStrings[key.ToLower()];
            return connectionStringFromConfig == null ? null : connectionStringFromConfig.ConnectionString;
        }
    }
}